package com.hsurosy.infrastructure.persistent.repository;

import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import com.alibaba.fastjson.JSON;
import com.hsurosy.domain.award.model.valobj.AccountStatusVO;
import com.hsurosy.domain.credit.model.aggregate.TradeAggregate;
import com.hsurosy.domain.credit.model.entity.CreditAccountEntity;
import com.hsurosy.domain.credit.model.entity.CreditOrderEntity;
import com.hsurosy.domain.credit.model.entity.TaskEntity;
import com.hsurosy.domain.credit.repository.ICreditRepository;
import com.hsurosy.domain.credit.service.ICreditAdjustService;
import com.hsurosy.infrastructure.event.EventPublisher;
import com.hsurosy.infrastructure.persistent.dao.IRaffleActivityAccountDao;
import com.hsurosy.infrastructure.persistent.dao.ITaskDao;
import com.hsurosy.infrastructure.persistent.dao.IUserCreditAccountDao;
import com.hsurosy.infrastructure.persistent.dao.IUserCreditOrderDao;
import com.hsurosy.infrastructure.persistent.po.Task;
import com.hsurosy.infrastructure.persistent.po.UserCreditAccount;
import com.hsurosy.infrastructure.persistent.po.UserCreditOrder;
import com.hsurosy.infrastructure.persistent.redis.IRedisService;
import com.hsurosy.types.common.Constants;
import com.hsurosy.types.enums.ResponseCode;
import com.hsurosy.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**
 * @Author Hsu琛君珩
 * @Date 2024-11-24 18:33
 * @Description 用户积分仓储
 * @Version: v1.0.0
 */
@Slf4j
@Repository
public class CreditRepository implements ICreditRepository {

    @Resource
    private IRedisService redisService; // Redis 服务接口，用于分布式锁
    @Resource
    private IUserCreditAccountDao userCreditAccountDao; // 积分账户 DAO 层
    @Resource
    private IUserCreditOrderDao userCreditOrderDao; // 积分订单 DAO 层
    @Resource
    private IDBRouterStrategy dbRouter; // 数据库路由策略，用于分库分表
    @Resource
    private TransactionTemplate transactionTemplate; // 事务模板，确保数据库操作的一致性
    @Resource
    private ITaskDao taskDao; // 任务数据访问层
    @Resource
    private EventPublisher eventPublisher; // 事件发布器

    /**
     * 保存用户积分交易订单
     *
     * @param tradeAggregate 包含用户积分账户和交易订单的聚合对象
     */
    @Override
    public void saveUserCreditTradeOrder(TradeAggregate tradeAggregate) {
        String userId = tradeAggregate.getUserId(); // 获取用户ID
        CreditAccountEntity creditAccountEntity = tradeAggregate.getCreditAccountEntity(); // 获取积分账户实体
        CreditOrderEntity creditOrderEntity = tradeAggregate.getCreditOrderEntity(); // 获取积分订单实体
        TaskEntity taskEntity = tradeAggregate.getTaskEntity(); // 获取任务实体

        // 构建积分账户请求对象
        UserCreditAccount userCreditAccountReq = new UserCreditAccount();
        userCreditAccountReq.setUserId(userId);
        userCreditAccountReq.setTotalAmount(creditAccountEntity.getAdjustAmount());
        // 仓储往上有业务语义，仓储往下到 DAO 操作是没有业务语义的，所以不用在意这块的字段名称，直接用持久化对象即可
        userCreditAccountReq.setAvailableAmount(creditAccountEntity.getAdjustAmount());
        userCreditAccountReq.setAccountStatus(AccountStatusVO.open.getCode()); // 设置账户状态为“开启”

        // 构建积分订单请求对象
        UserCreditOrder userCreditOrderReq = new UserCreditOrder();
        userCreditOrderReq.setUserId(creditOrderEntity.getUserId());
        userCreditOrderReq.setOrderId(creditOrderEntity.getOrderId());
        userCreditOrderReq.setTradeName(creditOrderEntity.getTradeName().getName());
        userCreditOrderReq.setTradeType(creditOrderEntity.getTradeType().getCode());
        userCreditOrderReq.setTradeAmount(creditOrderEntity.getTradeAmount());
        userCreditOrderReq.setOutBusinessNo(creditOrderEntity.getOutBusinessNo());

        // 构建任务对象
        Task task = new Task();
        task.setUserId(taskEntity.getUserId());
        task.setTopic(taskEntity.getTopic());
        task.setMessageId(taskEntity.getMessageId());
        task.setMessage(JSON.toJSONString(taskEntity.getMessage()));
        task.setState(taskEntity.getState().getCode());

        // 获取分布式锁，确保并发操作的安全性
        RLock lock = redisService.getLock(Constants.RedisKey.USER_CREDIT_ACCOUNT_LOCK + userId + Constants.UNDERLINE + creditOrderEntity.getOutBusinessNo());
        try {
            lock.lock(3, TimeUnit.SECONDS); // 加锁，超时时间为3秒
            dbRouter.doRouter(userId); // 设置数据库路由
            transactionTemplate.execute(status -> {
                try {
                    // 1. 查询用户积分账户是否存在
                    UserCreditAccount userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
                    if (null == userCreditAccount) {
                        // 如果账户不存在，插入新记录
                        userCreditAccountDao.insert(userCreditAccountReq);
                    } else {
                        BigDecimal availableAmount = userCreditAccountReq.getAvailableAmount();
                        // 如果账户存在，更新可用积分
                        if (availableAmount.compareTo(BigDecimal.ZERO) >= 0) {
                            userCreditAccountDao.updateAddAmount(userCreditAccountReq); // 增加可用积分
                        } else {
                            // 如果积分不足，进行扣减操作，并确保更新成功
                            int subtractionCount = userCreditAccountDao.updateSubtractionAmount(userCreditAccountReq);
                            if (1 != subtractionCount) {
                                status.setRollbackOnly(); // 更新失败时回滚事务
                                throw new AppException(ResponseCode.USER_CREDIT_ACCOUNT_NO_AVAILABLE_AMOUNT.getCode(), ResponseCode.USER_CREDIT_ACCOUNT_NO_AVAILABLE_AMOUNT.getInfo());
                            }
                        }
                    }
                    // 2. 插入积分订单记录
                    userCreditOrderDao.insert(userCreditOrderReq);
                    // 3. 写入任务
                    taskDao.insert(task);
                } catch (DuplicateKeyException e) {
                    // 捕获唯一索引冲突异常，回滚事务
                    status.setRollbackOnly();
                    log.error("调整账户积分额度异常，唯一索引冲突 userId: {}, orderId: {}", userId, creditOrderEntity.getUserId(), e);
                } catch (Exception e) {
                    // 捕获其他异常并回滚事务
                    status.setRollbackOnly();
                    log.error("调整账户积分额度失败 userId: {} orderId: {}", userId, creditOrderEntity.getOrderId(), e);
                }
                return 1; // 返回事务执行成功标志
            });
        } finally {
            dbRouter.clear(); // 清除数据库路由设置
            if (lock.isLocked()) {
                lock.unlock(); // 释放锁
            }
        }

        try {
            // 在事务外发送消息，如果失败会有任务补偿
            eventPublisher.publish(task.getTopic(), task.getMessage());
            // 更新任务状态为完成
            taskDao.updateTaskSendMessageCompleted(task);
            log.info("调整账户积分记录，发送MQ消息完成 userId: {} orderId: {} topic: {}", userId, creditOrderEntity.getOrderId(), task.getTopic());
        } catch (Exception e) {
            log.error("调整账户积分记录，发送MQ消息失败 userId: {} topic: {}", userId, task.getTopic());
            // 更新任务状态为失败
            taskDao.updateTaskSendMessageFail(task);
        }
    }

    /**
     * 查询用户积分账户信息
     *
     * @param userId 用户ID
     * @return 包含用户积分账户信息的实体对象
     */
    @Override
    public CreditAccountEntity queryUserCreditAccount(String userId) {
        // 创建查询请求对象
        UserCreditAccount userCreditAccountReq = new UserCreditAccount();
        userCreditAccountReq.setUserId(userId);
        try {
            // 路由到指定数据库分片
            dbRouter.doRouter(userId);
            // 查询用户积分账户信息
            UserCreditAccount userCreditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountReq);
            // 默认可用积分为0，如果用户账户存在，则返回实际的可用积分额度
            BigDecimal availableAmount = BigDecimal.ZERO;
            if (null != userCreditAccount) {
                availableAmount = userCreditAccount.getAvailableAmount();
            }
            // 返回封装好的积分账户信息实体，包含用户ID和调整后的可用积分额度
            return CreditAccountEntity.builder()
                    .userId(userId)
                    .adjustAmount(availableAmount)
                    .build();
        } finally {
            // 清理数据库路由信息，防止线程池污染
            dbRouter.clear();
        }
    }

}
